#!/usr/bin/env node

const childProcess = require('child_process');
const fs = require('fs');
const program = require('commander');
const path = require('path');

const evmConfig = require('./evm-config');
const { color, fatal } = require('./utils/logging');
const depot = require('./utils/depot-tools');
const goma = require('./utils/goma');
const { refreshPathVariable } = require('./utils/refresh-path');

// Refresh the PATH variable at the top of this shell so that retries in the same shell get the latest PATH variable
refreshPathVariable();

function maybeCheckForUpdates() {
  // skip auto-update check if disabled
  const disableAutoUpdatesFile = path.resolve(__dirname, '..', '.disable-auto-updates');
  if (fs.existsSync(disableAutoUpdatesFile)) {
    return;
  }
  // don't check if we already checked recently
  const intervalHours = 4;
  const updateCheckTSFile = path.resolve(__dirname, '..', '.update');
  const lastCheckEpochMsec = fs.existsSync(updateCheckTSFile)
    ? parseInt(fs.readFileSync(updateCheckTSFile, 'utf8'), 10)
    : 0;
  const needCheckAfter = lastCheckEpochMsec + 1000 * 60 * 60 * intervalHours;
  if (Date.now() < needCheckAfter) {
    return;
  }

  // Run the updater script.
  //
  // NB: send updater's stdout to stderr so its log messages are visible
  // but don't pollute stdout. For example, calling `FOO="$(e show exec)"`
  // should not get a FOO that includes "Checking for build-tools updates".
  childProcess.spawnSync(process.execPath, ['e-auto-update.js'], {
    cwd: __dirname,
    stdio: [0, 2, 2],
  });

  // update the last-checked-at timestamp
  fs.writeFileSync(updateCheckTSFile, `${Date.now()}`);

  // re-run the current invocation with the updated build-tools
  const result = childProcess.spawnSync(
    process.execPath,
    process.argv[0] === process.execPath ? process.argv.slice(1) : process.argv,
    {
      cwd: process.cwd(),
      stdio: 'inherit',
    },
  );
  process.exit(result.status);
}

maybeCheckForUpdates();

program.description('Electron build tool').usage('<command> [commandArgs...]');

program
  .command('init [options] <name>', 'Create a new build config')
  .alias('new')
  .command('sync [gclientArgs...]', 'Get or update source code')
  .command('build [options]', 'Build Electron and other things')
  .alias('make');

program
  .command('start')
  .alias('run')
  .description('Run the Electron executable')
  .allowUnknownOption()
  .action(() => {
    try {
      const exec = evmConfig.execOf(evmConfig.current());
      const args = program.rawArgs.slice(3);
      const opts = { stdio: 'inherit' };
      console.log(color.childExec(exec, args, opts));
      childProcess.execFileSync(exec, args, opts);
    } catch (e) {
      fatal(e);
    }
  })
  .on('--help', () => {
    console.log('');
    console.log('Examples:');
    console.log('');
    console.log('  $ e start .');
    console.log('  $ e start /path/to/app');
    console.log('  $ e start /path/to/app --js-flags');
  });

program
  .command('node')
  .description('Run the Electron build as if it were a Node.js executable')
  .allowUnknownOption()
  .action(() => {
    try {
      const exec = evmConfig.execOf(evmConfig.current());
      const args = program.rawArgs.slice(3);
      const opts = {
        env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
        stdio: 'inherit',
      };
      console.log(color.childExec(exec, args, opts));
      childProcess.execFileSync(exec, args, opts);
    } catch (e) {
      fatal(e);
    }
  })
  .on('--help', () => {
    console.log('');
    console.log('Examples:');
    console.log('');
    console.log('  $ e node .');
    console.log('  $ e node /path/to/app');
  });

program.command('debug', 'Run the Electron build with a debugger (gdb or lldb)');

program
  .command('use <name>')
  .description('Use build config <name> when running other `e` commands')
  .action(name => {
    try {
      evmConfig.setCurrent(name);
      console.log(`Now using config ${color.config(name)}`);
    } catch (e) {
      fatal(e);
    }
  });

program
  .command('remove <name>')
  .alias('rm')
  .description('Remove build config <name> from list')
  .action(name => {
    try {
      evmConfig.remove(name);
      console.log(`Removed config ${color.config(name)}`);
    } catch (e) {
      fatal(e);
    }
  });

program
  .command('show <subcommand>', 'Show info about the current build config')
  .command('test [specRunnerArgs...]', `Run Electron's spec runner`)
  .command('pr [options]', 'Open a GitHub URL where you can PR your changes')
  .command('patches <basename>', 'Refresh the patches in $root/src/electron/patches/$basename')
  .command('open <sha1|PR#>', 'Open a GitHub URL for the given commit hash / pull # / issue #')
  .command(
    'load-xcode',
    'Loads required versions of Xcode and the macOS SDK and symlinks them.  This may require sudo',
  )
  .command('auto-update', 'Check for build-tools updates or enable/disable automatic updates')
  .alias('check-for-updates')
  .command(
    'cherry-pick <patch-url> <target-branch> [additionBranches...]',
    'Opens a PR to electron/electron that backport the given CL into our patches folder',
  )
  .alias('auto-cherry-pick');

program
  .command('update-goma')
  .description('Ensure a fresh copy of Goma is installed')
  .action(() => {
    const args = process.argv.slice(3);
    const config = {};
    if (args.length > 0 && args[0] === 'msftGoma') {
      config.gomaSource = 'msft';
    }
    console.log(`goma checksum: ${goma.downloadAndPrepare(config)}`);
  });

program
  .command('sanitize-config [name]')
  .description('Update and overwrite an existing config to conform to latest build-tools updates')
  .action(name => {
    try {
      const configName = name || evmConfig.currentName();
      evmConfig.sanitizeConfig(configName, true);
      console.log(`${color.success} Sanitized contents of ${color.config(configName)}`);
    } catch (e) {
      fatal(e);
    }
  });

program
  .command('npm')
  .description(
    'Run a command that eventually spawns the electron NPM package but override the Electron binary that is used to be your local from-source electron',
  )
  .allowUnknownOption()
  .helpOption('\0')
  .action(() => {
    const args = process.argv.slice(3);
    if (args.length === 0) {
      console.error(`${color.err} Must provide a command to 'e npm'`);
      process.exit(1);
    }

    const { status, error } = depot.spawnSync(evmConfig.current(), args[0], args.slice(1), {
      stdio: 'inherit',
      env: {
        ELECTRON_OVERRIDE_DIST_PATH: evmConfig.outDir(evmConfig.current()),
      },
    });
    if (status !== 0) {
      console.error(
        `${color.err} Failed to run command, exit code was "${status}", error was '${error}'`,
      );
    }
    process.exit(status);
  });

program
  .command('depot-tools')
  .alias('d')
  .description('Run a command from the depot-tools directory with the correct configuration')
  .allowUnknownOption()
  .helpOption('\0')
  .action(() => {
    const args = process.argv.slice(3);
    if (args.length === 0) {
      console.error(`${color.err} Must provide a command to 'e depot-tools'`);
      process.exit(1);
    }
    let cwd;
    if (args[0] === 'goma_ctl' || args[0] === 'goma_auth') {
      goma.downloadAndPrepare(evmConfig.current());
      cwd = goma.dir;
      args[0] = `${args[0]}.py`;
      args.unshift('python');
    }

    if (args[0] === '--') {
      args.shift();
    }

    const { status, error } = depot.spawnSync(evmConfig.current(), args[0], args.slice(1), {
      cwd,
      stdio: 'inherit',
      env: {
        ...process.env,
        AGREE_NOTGOMA_TOS: '1',
      },
    });
    if (status !== 0) {
      console.error(
        `${color.err} Failed to run command, exit code was "${status}", error was '${error}'`,
      );
    }
    process.exit(status);
  });

program.on('--help', () => {
  console.log(`
See https://github.com/electron/build-tools/blob/master/README.md for usage.`);
});

program.parse(process.argv);
